{-# LANGUAGE DeriveDataTypeable   #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-|
    Module      :  Numeric.ER.Real.Base.CombinedMachineAP
    Description :  auto-switching hardware-software floats 
    Copyright   :  (c) Michal Konecny
    License     :  BSD3

    Maintainer  :  mikkonecny@gmail.com
    Stability   :  experimental
    Portability :  non-portable (requires fenv.h)

    Arbitrary precision floating point numbers that use
    machine double up to its precision.  When a higher
    granularity is required, it automatically switches 
    to another floating point type.
-}
module Numeric.ER.Real.Base.CombinedMachineAP 
(
    ERMachineAP,
    doubleDigits
)
where

import qualified Numeric.ER.Real.Base as B
import qualified Numeric.ER.BasicTypes.ExtendedInteger as EI
import Numeric.ER.Real.Base.MachineDouble
import Numeric.ER.Real.Base.Float
import Numeric.ER.BasicTypes
import Numeric.ER.Misc

import Data.Typeable
import Data.Generics.Basics
import Data.Binary
--import BinaryDerive

import Data.Ratio

data ERMachineAP b =
    ERMachineAPMachineDouble
    {
        machapfltDoubleGranularity :: Granularity
        {-^ this has to be between 1 and 'doubleDigits' -}
    ,
        machapfltDouble :: Double
    }
    |    
    ERMachineAPB
    {
        machapfltB :: b
    }
    deriving (Typeable, Data)

doubleDigits = floatDigits (0 :: Double)

{- the following has been generated by BinaryDerive -}     
instance (Binary b) => (Binary (ERMachineAP b)) where
  put (ERMachineAPMachineDouble a b) = putWord8 0 >> put a >> put b
  put (ERMachineAPB a) = putWord8 1 >> put a
  get = do
    tag_ <- getWord8
    case tag_ of
      0 -> get >>= \a -> get >>= \b -> return (ERMachineAPMachineDouble a b)
      1 -> get >>= \a -> return (ERMachineAPB a)
      _ -> fail "no parse"
{- the above has been generated by BinaryDerive -}
    
lift1ERMachineAP ::
    (Double -> Double) ->
    (b -> b) ->
    (ERMachineAP b -> ERMachineAP b)
lift1ERMachineAP fD fB (ERMachineAPMachineDouble g d) = 
    ERMachineAPMachineDouble g (fD d) 
lift1ERMachineAP fD fB (ERMachineAPB b) = 
    ERMachineAPB (fB b) 

op1ERMachineAP ::
    (Double -> a) ->
    (b -> a) ->
    (ERMachineAP b -> a)
op1ERMachineAP fD fB (ERMachineAPMachineDouble g d) = 
    fD d 
op1ERMachineAP fD fB (ERMachineAPB b) = 
    fB b 

lift2ERMachineAP ::
    (B.ERRealBase b) =>
    (Double -> Double -> Double) ->
    (b -> b -> b) ->
    (ERMachineAP b -> ERMachineAP b -> ERMachineAP b)
lift2ERMachineAP fD fB 
        (ERMachineAPMachineDouble g1 d1) (ERMachineAPMachineDouble g2 d2) = 
    ERMachineAPMachineDouble (max g1 g2) (fD d1 d2)
lift2ERMachineAP fD fB 
        (ERMachineAPMachineDouble g1 d1) (ERMachineAPB b2) = 
    ERMachineAPB $ fB (B.fromDouble d1) b2
lift2ERMachineAP fD fB 
        (ERMachineAPB b1) (ERMachineAPMachineDouble g2 d2) = 
    ERMachineAPB $ fB b1 (B.fromDouble d2)
lift2ERMachineAP fD fB 
        (ERMachineAPB b1) (ERMachineAPB b2) = 
    ERMachineAPB $ fB b1 b2
    
op2ERMachineAP ::
    (B.ERRealBase b) =>
    (Double -> Double -> a) ->
    (b -> b -> a) ->
    (ERMachineAP b -> ERMachineAP b -> a)
op2ERMachineAP fD fB 
        (ERMachineAPMachineDouble g1 d1) (ERMachineAPMachineDouble g2 d2) = 
    fD d1 d2
op2ERMachineAP fD fB 
        (ERMachineAPMachineDouble g1 d1) (ERMachineAPB b2) = 
    fB (B.fromDouble d1) b2
op2ERMachineAP fD fB 
        (ERMachineAPB b1) (ERMachineAPMachineDouble g2 d2) = 
    fB b1 (B.fromDouble d2)
op2ERMachineAP fD fB 
        (ERMachineAPB b1) (ERMachineAPB b2) = 
    fB b1 b2
    
instance (B.ERRealBase b) => Show (ERMachineAP b)
    where
    show = showERMachineAP 6 True True
    
showERMachineAP numDigits showGran showComponents =
    showEMA
    where
    maybeGran gr
        | showGran = "{g=" ++ show gr ++ "}"
        | otherwise = ""
    maybeComps
        | showComponents = "{Double}"
        | otherwise = ""
    showEMA (ERMachineAPMachineDouble gr d) = 
        show d ++ (maybeGran gr) ++ maybeComps
    showEMA (ERMachineAPB b) = 
        B.showDiGrCmp numDigits showGran showComponents b


instance (B.ERRealBase b) => Eq (ERMachineAP b)
    where
    (==) = op2ERMachineAP (==) (==)
    
instance (B.ERRealBase b) => Ord (ERMachineAP b)
    where
    compare = op2ERMachineAP compare compare
    

    
instance (B.ERRealBase b) => Num (ERMachineAP b)
    where
    fromInteger n 
        | gran < doubleDigits = 
            ERMachineAPMachineDouble gran $ fromInteger n
        | otherwise = 
            ERMachineAPB b
        where
        gran = B.getGranularity b    
        b = fromInteger n
    abs = lift1ERMachineAP abs abs  
    signum = lift1ERMachineAP signum signum
    negate = lift1ERMachineAP negate negate
    (+) = lift2ERMachineAP (+) (+)
    (*) = lift2ERMachineAP (*) (*)
    
instance (B.ERRealBase b) => Fractional (ERMachineAP b)
    where
    fromRational rat =
        (fromInteger $ numerator rat) 
        / (fromInteger $ denominator rat)
    recip = lift1ERMachineAP recip recip
    (/) = lift2ERMachineAP (/) (/)
        
instance (B.ERRealBase b, Real b) => Real (ERMachineAP b)
    where
    toRational = op1ERMachineAP toRational toRational
    
instance (B.ERRealBase b, RealFrac b) => RealFrac (ERMachineAP b)
    where
    properFraction (ERMachineAPMachineDouble g d) =
        (a, ERMachineAPMachineDouble g remainder)
        where
        (a,remainder) = properFraction d 
    properFraction (ERMachineAPB b) =
        (a, ERMachineAPB remainder)
        where
        (a,remainder) = properFraction b 
        
        
instance (B.ERRealBase b) => B.ERRealBase (ERMachineAP b)
    where
    typeName _ = "auto switching double and " ++ (B.typeName (0::b))
    initialiseBaseArithmetic x = 
		do
		putStr $ "Base arithmetic:" ++ B.typeName x ++ "; "
		initMachineDouble
    defaultGranularity _ = (B.defaultGranularity (0 :: b))
    getApproxBinaryLog = 
        op1ERMachineAP doubleBinaryLog B.getApproxBinaryLog
        where
        doubleBinaryLog d
            | d < 0 =
                error $ "ERMachineAP: getApproxBinaryLog: negative argument " ++ show d 
            | d == 0 = EI.MinusInfinity 
            | d >= 1 =
                fromInteger $ intLogUp 2 $ ceiling d
            | d < 1 =
                negate $ fromInteger $ intLogUp 2 $ ceiling $ recip d
    getGranularity (ERMachineAPB b) = B.getGranularity b
    getGranularity (ERMachineAPMachineDouble gr _) = gr
    setMinGranularity gran (ERMachineAPMachineDouble g d) 
        | gran > doubleDigits =
            ERMachineAPB $ B.setMinGranularity gran $ B.fromDouble d
        | otherwise =
            ERMachineAPMachineDouble gran d
    setMinGranularity gran (ERMachineAPB b) = ERMachineAPB $ B.setMinGranularity gran b 
    setGranularity gran (ERMachineAPMachineDouble g d) 
        | gran > doubleDigits =
            ERMachineAPB $ B.setGranularity gran $ B.fromDouble d
        | otherwise =
            ERMachineAPMachineDouble gran d
    setGranularity gran (ERMachineAPB b)
        | gran <= doubleDigits =
            ERMachineAPMachineDouble gran $ B.toDouble b
        | otherwise = 
            ERMachineAPB $ B.setGranularity gran b 
    getMaxRounding _ = 
        error "ERMachineAP: getMaxRounding not implemented yet"
    isERNaN = op1ERMachineAP isNaN B.isERNaN
    erNaN = B.fromDouble (0/0)
    isPlusInfinity = 
        op1ERMachineAP (== 1/0) B.isPlusInfinity
    plusInfinity = B.fromDouble $ 1/0
    fromIntegerUp = fromInteger
    fromDouble d = 
        ERMachineAPMachineDouble (B.defaultGranularity (0 :: b)) d
    toDouble = op1ERMachineAP id B.toDouble
    fromFloat f = 
        ERMachineAPMachineDouble (B.defaultGranularity (0 :: b)) $
            fromRational $ toRational f
    toFloat = op1ERMachineAP (fromRational . toRational) B.toFloat 
    showDiGrCmp = showERMachineAP
    